1 /**
2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This module contains the registry of analaysers
7 */
8 module code_checker.engine.registry;
9 
10 import logger = std.experimental.logger;
11 import std.exception : collectException;
12 
13 import code_checker.engine.types;
14 
15 @safe:
16 
17 /// The type of an analyser which then affect the order they are executed.
18 enum Type {
19     staticCode,
20     dynamic,
21 }
22 
23 struct Registry {
24     private {
25         BaseFixture[][Type] analysers;
26     }
27 
28     void put(BaseFixture a, Type t) {
29         assert(a !is null);
30 
31         if (auto v = t in analysers) {
32             (*v) ~= a;
33         } else {
34             analysers[t] = [a];
35         }
36     }
37 
38     /// Range over the analysers. !
39     auto range() {
40         import std.array : array;
41         import std.algorithm : map, joiner, filter;
42 
43         const order = [Type.staticCode, Type.dynamic];
44 
45         auto getAnalysers(Type t) {
46             if (auto v = t in analysers)
47                 return (*v).map!(a => AnalyserRange.Pair(t, a)).array;
48             return null;
49         }
50 
51         return order.map!(a => getAnalysers(a)).filter!(a => a !is null).joiner.array;
52     }
53 }
54 
55 /// Returns: The total status of running the analyzers.
56 Status execute(Environment env, ref Registry reg) @trusted {
57     import std.algorithm;
58     import std.range;
59 
60     TotalResult tres;
61 
62     void handleResult(Result res_) nothrow {
63         // we know the thread finished and have the only copy.
64         // immutable is a bit cumbersome for now so throw away it to keep the
65         // code somewhat efficient.
66         auto res = cast() res_;
67 
68         try {
69             log(res.msg);
70 
71             tres.status = mergeStatus(tres.status, res.status);
72             tres.score = Score(tres.score + res.score);
73             tres.sugg ~= res.msg.array.filter!(a => a.severity == MsgSeverity.improveSuggestion)
74                 .array;
75 
76             logger.trace(res);
77             logger.trace(tres);
78         } catch (Exception e) {
79             logger.warning("Failed executing all tests").collectException;
80             logger.warning(e.msg).collectException;
81             tres.status = Status.failed;
82         }
83     }
84 
85     foreach (a; reg.range) {
86         logger.infof("%s: %s", a.type, a.analyzer.explain);
87         a.analyzer.putEnv(env);
88         handleResult(executeOneAnalyzer(a.analyzer));
89     }
90 
91     log(tres);
92     return tres.status;
93 }
94 
95 Result executeOneAnalyzer(BaseFixture a) nothrow @trusted {
96     Result r;
97     try {
98         a.setup;
99         a.execute;
100         a.tearDown;
101         r = a.result;
102     } catch (Exception e) {
103         logger.error(e.msg).collectException;
104         r.status = Status.failed;
105     }
106 
107     return r;
108 }
109 
110 private:
111 
112 void log(Messages msgs) {
113     import std.algorithm : sort;
114 
115     foreach (m; msgs.value.sort) {
116         final switch (m.severity) {
117         case MsgSeverity.improveSuggestion:
118             break;
119         case MsgSeverity.unableToExecute:
120         case MsgSeverity.failReason:
121             logger.warning(m.value);
122             break;
123         }
124     }
125 }
126 
127 void log(TotalResult tres) {
128     import std.conv : to;
129     import colorize;
130 
131     logger.infof("Executing analysers %s", tres.status == Status.failed
132             ? "Failed".color(Color.red) : "Passed".color(Color.green));
133 
134     if (tres.sugg.length > 0) {
135         logger.info("Suggestions for how to improve the score");
136         foreach (m; tres.sugg)
137             logger.info("    ", m.value);
138     }
139 
140     if (tres.status == Status.passed) {
141         logger.info("Congratulations!!!");
142         logger.infof("Your code reached Quality Level %s", 1 + tres.score / 10);
143     }
144 
145     const string score = () {
146         if (tres.score < 0)
147             return tres.score.to!string.color(Color.red, Background.init, Mode.bold);
148         return tres.score.to!string;
149     }();
150     logger.infof("You scored %s points", score);
151 
152     if (tres.score < 0) {
153         logger.info("Sorry, your code needs a major rework to reach an acceptable quality level");
154     }
155 }
156 
157 /// Input range over the analysers.
158 struct AnalyserRange {
159     import std.typecons : Tuple;
160 
161     alias Pair = Tuple!(Type, "type", BaseFixture, "analyzer");
162 
163     Pair[] r;
164 
165     auto front() @safe pure nothrow {
166         assert(!empty, "Can't get front of an empty range");
167         return r[0];
168     }
169 
170     void popFront() @safe pure nothrow {
171         assert(!empty, "Can't pop front of an empty range");
172         r = r[1 .. $];
173     }
174 
175     bool empty() @safe pure nothrow const @nogc {
176         return r.length == 0;
177     }
178 }